Помогни ни да направим Uroci.net по - богат! Добави урок

C++ част.7 (Член функции на клас)

C++ » C++
fix3d   трудност:    видян: 43581



7.2. Почленова инициализация

Има един случай, при който конструкторите, предложени от проектанта на класа не се извикват за инициализиране на новодефиниран обект на клас - случая, когато обекта на даден клас се инициализира чрез друг обект от същия клас. Например,

String vowel( "a" );
String article = vowel;

Инициализацията на article се извършва чрез последователно копиране на всеки член на vowel в съответния член на article. Това се нарича почленова инициализация. Компилаторът изпълнява почленовото инициализиране чрез дефинирането на един специален конструктор със следната обща форма.

X::X( const X& );
За класа String конструкторът е дефиниран някак така:
Stirng::String( const String& )
{ len = s.len;
str = s.str; }

Реализирането на обект на клас чрез друг обект от същия клас може да бъде необходимо в три случая:

1. Явно инициализиране на един обект на клас чрез друг. Например,

// String::String( char* );
String color( "blue" );
// memberwise initialization generated
String mood = color;

2. Изпращане на обект на клас като аргумент на функция. Например:

extern int count( Stirng s, char ch );
// local instance of s <== mood
int occurs = count( mood, ‘e’ );

3. Връщане на обект на клас от функция. Например, extern String sub( String&, char, char );

main() {
String rriver( "mississippi" );
cout << river << " " << sub( river, ‘i’, ‘I’ ) << "n"; }

Нито изпращането на аргумент псевдоним, нито връщането на псевдоним, обаче, не предизвиква иниициализация на обект. Това се дължи на факта, че изпращането-чрез-псевдоним за разлика от изпращането-чрез-стойност не създава локално копие на обекта. (Раздел 4.6. разглежда изпращането-чрез-псевдоним).

Почленовото инициализиране копира всички вградени или произлезли член данни от един обект на клас в друг. Членовете класове, обаче, не се копират; а по-скоро почленовото инициализиране се прилага рекурсивно. Например, класът Word дефинира един член цяло число, occurs, и един член от типа на класа String, name. Ето дефинициите на двата обекта на Word:

Word noun( "book" );
Word vern = noun;

verb се инициализира на следните стъпки:

1. Членът occurs се инициализира със стойността на noun.occurs.

2. Членът name се инициализира почленово чрез вътрешно генерирания конструктор за класа String.

Почленовото инициализиране по подразбиране понякога е недостатъчно. Фиг. 7.1 илюстрира паметта, отделена за count и verb. Съществуват дава проблема:

1. count на noun не трябва да бъде копиран в count на verb. Фактически, двете стойности са разделени. Почленовият механизъм по подразбиране нарушава семантиката на класа Word.

2. Членът str на noon и verb адресира една и съща памет. Това ще създаде сериозни проблеми ако двата обекта на класа не излизат от обхват по едно и също време.

Изобщо казано почленоват инициализация по подразбиране не е достатъчна за класове, които съдържат членове указатели или дефинират деструктор. Това се дължи на факта, че деструкторът се вика за всеки обект на клас, даже и когато той е инициализиран почленово, а не “конструиран”.

Както показва фиг. 7.1, това означава, че паметта, адресирана, от два или повече обекта на класа се “деструктурира” два или повече пъти. В някои случаи могат да се получат висящи указатели. В други случаи може да бъде деструктурирана памет, отделена в последствие за други цели. И в двата случая програмата вероятно няма да работи правилно. Този проблем може да бъде решен, като проектантът на класа създаде представител на конструктор за явна почленова инициализация. Това е тема на следващия подраздел.

Специалният конструктор X(const X&)

Както вече видяхме, при някои обстоятелства, класовете изискват различно управление на инициализирането на обекти чрез други обекти, отколкото се предлага от почленовата инициализация по подразбиране. Един клас допуска това допълнително управление чрез дефинирането на явен представител на конструктора X(const X&). Ако такъв конструктор е явно дефиниран в един клас, той се вика за всяко инициализиране на един обект на клас чрез друг. Например,

String::String( const String& s )
{ len = s.len;
str = new char[ len + 1 ];
strcpy( str, s.str ); }
String(const String& ) се извиква винаги, когато един обект на String се инициализира чрез друг. Всеки член str ще адресира различна област от паметта.

Упражнение 7-7. Реализирайте конструктор Screen(const Screen&) за класа Screen, дефиниран в глава 6. Дайте пример за три случая, в които се вика този конструктор.

Упражнение 7-8. Реализирайте конструктор IntList(const IntList&) за класа IntList, дефиниран в глава 5. Дайте пример за три случая, в които се вика този конструктор.

X(const X&) и обекти членове на клас

В този подраздел, ние ще разгледаме два примера на X(const X&) за класове, съдържащи обекти членове на клас:

1. Външният клас не дефинира представител на X(const X&), но класът член дефинира.
2. Двата класа дефинират представител на X(const X&).

В първия случай, илюстриран чрез дефиницията на класа Word, Word е без Word(const Word&). към члена клас, String, обаче, е добавена дефиниция на String(const String&).

class Word {
public: Word( char *s, int cnt = 0 ) : name(s), occurs(cnt) {}
Word( String& s, int cnt = 0 ) : name(s), occurs(cnt) {}
private:
int occurs;
String name; };

Инициализацията на един обект на Word чрез друг използува почленовата инициализация по подразбиране. Членът от клас String, обаче, се инициализира чрез извикване на String(const String&). Например,

String mystery( "rosebud" );
Word resolve( mystery );
extern search( Word wd );
search( resolve );

String(const String&) се извиква за инициализиране на члена name от resolve и на члена name от локалното копие на wd. Въобще, почленовата инициализация се прилага рекурсивно за всеки член обект на клас. За всеки член обект на клас, който дефинира представител на X(const X&), обаче, се извиква този представител, а не почленовия инициализатор.

Почленовата инициализация по подразбиране не се прилага по нататък ако явно е зададен представител на Word(const Word&). Представителят на Word отговаря за инициализацията на членовете си. Преди да илюстрираме правилното реализиране на представителя на Word, нека разгледаме следното неправилно решение:

// this implementation is incorrect
Word::Word( const Word& w )
{ occurs = 0;
name = w.name; }

Този представител неправилно инициализира члена name от класа String. Нека да разгледаме следния пример постъпково:

Word weather( "warm" );
Word feeling = wither;

Стъпките по инициализирането са следните:

1. feeling се разпознава като инициализиран с обекта на класа Word(const Word&). Има ли дефиниран представител на Word(const Word&)? Ако има, той се извиква; иначе, се прилага почленовото инициализиране. Word(const Word&) е намерен.

2. Има ли инициализационен списък? Не.

3. Има ли някакви членове обекти на клас? Да. name е обект на класа String.

4. В класа String дефиниран ли е конструктор, който не изисква аргументи? Ако не е се издава съобщение за грешка по време на компилация; иначе той се извиква.

5. Извиква се String() за инициализирането на feeling.name.

6. Извиква се Word(const Word&). Изпълнява се присвояването name = w.name. По подразбиране, както и при инициализацията, присвояването на обект на клас се извършва чрез почленово присвояване (вж. раздел 7.3 за подробности). String(const String&) никога не се извиква.

Това е втори пример, при който е важно разграничаването на фазите на инициализация и присвояване. (Раздел 7.1 разглежда първия - инициализацията на const и на членове псевдоними на клас). За да бъде извикан конструктора на обект на String name трябва да бъде инциализиран с w.name. Това означава, че name трябва да бъде включено в инициализационния списък. Правилната дефиниция на конструктор на обект на Word има вида:

Word::Word( const Word& w ) : name ( w.name )

// initialization {
// assignment occurs = w.occurs; }

Накратко, ако един външен клас не дефинира представител на X(const X&), всеки член обект на клас се инициализира почленно. Ако членът клас дефинира представетел на X(const X&), този представител ще бъде извикан. Ако външният клас не дефинира представител на X(const X&), обаче, той отговаря за явното инициализиране на члена си обект на клас поради своя инициализационен списък.

Упражнение 7-9. Реализирайте Buf(const Buf&) ( Упр. 7-2).
Упражнение 7-10. Реализирайте представителите на конструкторите INode(const INode&) и BinTree(const BinTree&). Обобщение на конструкторите и деструкторите

Специалният механизъм на конструкторите и деструкторите позволява автоматичната инициализация и освобождаване на обекти на клас. Конструкторите могат да бъдат презареждани за да предлагат набор от опции за инициализация; за по-голяма ефективност те могат да бъдат дефинирани inline. За членовете обекти на класове, които имат собствени конструктори, те се извикват преди конструктора на съдържащия ги клас. Редът на извикване на конструкторите на членовете обекти на клас зависи от реда на декларациите им. Деструкторите се извикват в обратен ред.

Всеки конструктор може да определи инициализационен списък, който се основава на механизма за изпращане на аргументи към конструкторите на членовете обекти. Този механизъм може да бъде използван за инициализиране на член данни, които не са обекти на клас, както и за const reference членове на клас.

В един от случаите не се извиква конструктор за нов обект на клас: когато този обект се инициализира чрез съществуващ обект на клас. Тогава се използува почленовата инициализация като стойността на всеки член клас се копира последователно. Ако един клас съдържа член, който е обект на клас се прилага рекурсивно почленовата инициализация за този обект.

Почленовото инициализиране може да създаде проблем, когато класовете съдържат членове указатели. Една и съща област от паметта може да бъде “деструктурирана” многократно. Тогава може да бъде дефиниран един специален конструктор за инициализиране на обект X(const X&), който да обработва този случай. Инициализацията на един обект на клас чрез друг извиква този специален конструктор, ако е дефиниран, вместо да прилага почленовото копиране по подразбиране.




Страници: «2 3 4 5 6 7 8 »

Сподели урока:



Регистрирайте се, за да добавите коментар


Калдейта Ком ЕООД - © 2003-. Всички права запазени.
Препоръчваме: IT Новини